// file:kernel/vmm.c
// autor:jiangxinpeng
// time:2021.3.4
// uptime:2021.6.22
// copyright:(C) by jiangxinpeng,All right are reserved.

#include <os/vmm.h>
#include <os/memspace.h>
#include <os/memcache.h>
#include <os/vmm.h>
#include <os/debug.h>
#include <os/process.h>
#include <os/virmem.h>
#include <os/share.h>
#include <os/safety.h>
#include <os/debug.h>
#include <arch/pymem.h>
#include <arch/vmm.h>
#include <lib/string.h>
#include <lib/list.h>
#include <lib/stddef.h>
#include <lib/type.h>
#include <lib/errno.h>

static int VmmIncShareMem(mem_space_t *space)
{
    address_t phyaddr = Vbase2Pybase(space->start);
    share_mem_t *share = ShareMemFindByAddr(phyaddr);
    if (!share)
    {
        return 0;
    }
    return ShareMemInc(share->id);
}

static int VmmDecShareMem(mem_space_t *space)
{
    address_t phyaddr = Vbase2Pybase(space->start);
    share_mem_t *share = ShareMemFindByAddr(phyaddr);
    if (!share)
    {
        return 0;
    }
    return ShareMemDec(share->id);
}

void VmmInit(vmm_t *vmm)
{
    vmm->page_storge = KernelPageCopyTo;
    list_init(&vmm->mem_space_head);
    list_init(&vmm->page_info_list);
    vmm->argu = NULL;
    vmm->envp = NULL;
    vmm->argbuff = NULL;
}

int VmmExit(vmm_t *vmm)
{
    if (vmm != NULL)
    {
        if (!list_empty(&vmm->mem_space_head))
        {
            if (VmmUnMapSpace(vmm) < 0)
            {
                KPrint(PRINT_WARNNING "vmm exit: when unmap sapce failed\n");
            }
            if (VmmReleaseSpace(vmm) < 0)
            {
                KPrint(PRINT_WARNNING "vmm exit: when release space failed\n");
            }
            return 0;
        }
    }
    return -1;
}

void VmmFree(vmm_t *vmm)
{
    if (vmm != NULL)
    {
        if (vmm->page_storge != NULL)
        {
            FreePage((uint32_t)KERNEL_VBASE2PYBASE(vmm->page_storge));
            vmm->page_storge = NULL;
        }
        KMemFree(vmm);
    }
}

static int DoCopyNormalPage(address_t vaddr, void *buf, vmm_t *child, vmm_t *parent)
{
    address_t paddr;
    memcpy(buf, (void *)vaddr, PAGE_SIZE);
    // switch to child page storage
    VmmActive(child);
    paddr = AllocUserPage(1);
    if (!paddr)
    {
        KPrint(PRINT_ERR "vmm_copy_mapping: page_alloc_one for vaddr failed!\n");
        VmmActive(parent);
        return -1;
    }
    VbaseLinkPybase(vaddr, paddr, PAGE_WRITE | PAGE_USERPAGE);
    memcpy((void *)vaddr, buf, PAGE_SIZE);
    VmmActive(parent);
    return 0;
}

static int DoCopySharePage(address_t vaddr, vmm_t *child, vmm_t *parent)
{
    address_t paddr = KERNEL_VBASE2PYBASE(vaddr);
    KPrint("[vmm]: copy share page at vaddr %x phy addr %x\n", vaddr, paddr);
    VmmActive(child);
    VbaseLinkPybase(vaddr, paddr, PAGE_WRITE | PAGE_USERPAGE);
    VmmActive(parent);
    return 0;
}

int VmmCopyMapSpace(vmm_t *child_vmm, vmm_t *parent_vmm)
{
    mem_space_t *p = NULL;
    list_traversal_all_owner_to_next(p, &parent_vmm->mem_space_head, list)
    {
        mem_space_t *space = MemSpaceAlloc();
        if (space == NULL)
        {
            KPrint(PRINT_ERR "%s: mem_alloc for space failed!\n", __func__);
            return -1;
        }
        *space = *p;
        if (space->flags & MEM_SPACE_MAP_SHARE)
        {
            if (VmmIncShareMem(space) < 0)
                return -1;
        }
        list_add_tail(&space->list, &child_vmm->mem_space_head);
    }
    return 0;
}

int VmmCopyMapping(task_t *child, task_t *parent)
{
    void *buf = KMemAlloc(PAGE_SIZE);
    if (buf == NULL)
    {
        KPrint(PRINT_ERR "%s: alloc buff failed!\n", __func__);
        return -1;
    }
    mem_space_t *space;
    address_t vaddr = 0;
    list_traversal_all_owner_to_next(space, &parent->vmm->mem_space_head, list)
    {
        vaddr = space->start;
        while (vaddr < space->end)
        {
            /* 如果是共享内存，就只复制页映射，而不创建新的页 */
            if (space->flags & MEM_SPACE_MAP_SHARE)
            {
                if (DoCopySharePage(vaddr, child->vmm, parent->vmm) < 0)
                {
                    KMemFree(buf);
                    return -1;
                }
            }
            else
            {
                if (DoCopyNormalPage(vaddr, buf, child->vmm, parent->vmm) < 0)
                {
                    KMemFree(buf);
                    return -1;
                }
            }
            vaddr += PAGE_SIZE;
        }
    }
    KMemFree(buf);
    return 0;
}

int VmmReleaseSpace(vmm_t *vmm)
{
    mem_space_t *space, *next;
    if (!vmm)
        return -1;

    list_traversal_all_owner_to_next_safe(space, next, &vmm->mem_space_head, list)
    {
        if (space->flags & MEM_SPACE_MAP_SHARE)
        {
            if (VmmDecShareMem(space) < 0)
            {
                KPrint(PRINT_ERR "share mem space  [%x-%x]release failed!\n", space->start, space->end);
            }
        }
        list_del_init(&space->list);
        MemSpaceFree(space);
    }
    VmmDeBuildArgBuff(vmm);
    list_init(&vmm->mem_space_head);
    vmm->code_start = 0;
    vmm->code_end = 0;
    vmm->data_start = 0;
    vmm->data_end = 0;
    vmm->heap_start = 0;
    vmm->heap_end = 0;
    vmm->stack_start = 0;
    vmm->stack_end = 0;
    return 0;
}

int VmmUnMapSpace(vmm_t *vmm)
{
    mem_space_t *space;
    if (vmm != NULL && (!list_empty(&vmm->mem_space_head)))
    {
        list_traversal_all_owner_to_next(space, &vmm->mem_space_head, list)
        {
            if (space->flags & MEM_SPACE_MAP_STACK || space->flags & MEM_SPACE_MAP_HEAP)
                VUnMemMap(space->start, space->end - space->start, 0);
            else
                VUnMemMap(space->start, space->end - space->start, space->flags & MEM_SPACE_MAP_SHARE);
        }
        return 0;
    }
    return -1;
}

int VmmUnMapOnlyMapSpace(vmm_t *vmm)
{
    mem_space_t *space;
    if (vmm != NULL && (!list_empty(&vmm->mem_space_head)))
    {
        list_traversal_all_owner_to_next(space, &vmm->mem_space_head, list)
        {
            if ((space->start >= vmm->map_start) && (space->end <= vmm->map_end))
                VUnMemMap(space->start, space->end - space->start, space->flags & MEM_SPACE_MAP_FIXED);
        }
        return 0;
    }
    return -1;
}

int VmmBuildArgBuff(vmm_t *vmm, char **argv, char **envp)
{
    char *buff = KMemAlloc(PAGE_SIZE);
    uint64_t arg_bottom;

    if (!buff)
        return -1;
    memset(buff, 0, PAGE_SIZE);
    ProcessBuildArg((address_t)buff + PAGE_SIZE, &arg_bottom, (const char **)envp, (const char ***)&vmm->envp);
    ProcessBuildArg(arg_bottom, NULL, (const char **)argv, (const char ***)&vmm->argu);
    vmm->argbuff = buff;
    return 0;
}

int VmmExitWhenForkFailed(vmm_t *child_vmm, vmm_t *parent_vmm)
{
    if (!child_vmm)
        return -1;
    if (list_empty(&child_vmm->mem_space_head))
        return -1;
    VmmActive(child_vmm);
    if (VmmUnMapSpace(child_vmm))
        KPrint(PRINT_ERR "vmm: exit when unmap space failed!\n");
    VmmActive(parent_vmm);
    if (VmmReleaseSpace(child_vmm))
        KPrint(PRINT_ERR "vmm: exit when release space failed!\n");
    VmmFree(child_vmm);
}

void VmmDeBuildArgBuff(vmm_t *vmm)
{
    if (vmm->argbuff)
    {
        KMemFree(vmm->argbuff);
        vmm->argbuff = NULL;
        vmm->argu = NULL;
        vmm->envp = NULL;
    }
}

void VmmDump(vmm_t *vmm)
{
    KPrint(PRINT_DEBUG "code: start=%x end=%x\n", vmm->code_start, vmm->code_end);
    KPrint(PRINT_DEBUG "data: start=%x end=%x\n", vmm->data_start, vmm->data_end);
    KPrint(PRINT_DEBUG "heap: start=%x end=%x\n", vmm->heap_start, vmm->heap_end);
    KPrint(PRINT_DEBUG "map: start=%x end=%x\n", vmm->map_start, vmm->map_end);
    KPrint(PRINT_DEBUG "stack: start=%x end=%x\n", vmm->stack_start, vmm->stack_end);
}

int SysMstatus(mstatus_t *ms)
{
    if (!ms)
        return -EINVAL;

    mstatus_t temp_ms;

    temp_ms.total = MemGetTotalSize();           // get pymem total size
    temp_ms.free = MemGetFreeSize();             // get pymem free size
    temp_ms.used = temp_ms.total - temp_ms.free; // get pymem used size

    if (temp_ms.used < 0)
        temp_ms.used = 0;

    KPrint("[mem] total %d free %d used %d\n", (uint32_t)temp_ms.total / MB, (uint32_t)temp_ms.free / MB, (uint32_t)temp_ms.used / MB);

    if (MemCopyToUser(ms, &temp_ms, sizeof(mstatus_t)) < 0)
    {
        return -EFAULT;
    }
    return 0;
}